﻿namespace Microsoft.VisualStudio.TestTools.UnitTesting
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Diagnostics.Contracts;

    /// <summary>
    /// A utility class that provides certain helpers for unit running unit tests.
    /// </summary>
    [DebuggerStepThrough]
    public static class TestRunner
    {
        #region Public Methods
        /// <summary>
        /// Runs the test against the test items that have already been added.
        /// </summary>
        /// <typeparam name="TArg1">The type of the first argument.</typeparam>
        /// <param name="testItems">The test items.</param>
        /// <param name="test">The test to run.</param>
        public static void Run<TArg1>(this IEnumerable<TArg1> testItems, Action<TArg1> test)
        {
            Contract.Requires(testItems != null);
            Contract.Requires(test != null);

            TestRunner.RunItems<TArg1>(
                testItems,
                testItem => test(testItem));
        }

        /// <summary>
        /// Runs the test against the test items that have already been added.
        /// </summary>
        /// <typeparam name="TArg1">The type of the first argument.</typeparam>
        /// <param name="testItems">The test items.</param>
        /// <param name="test">The test to run.</param>
        /// <returns>The collection of items.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design.")]
        public static ListItemCollection<Tuple<TArg1>> Run<TArg1>(this ListItemCollection<Tuple<TArg1>> testItems, Action<TArg1> test)
        {
            Contract.Requires(testItems != null);
            Contract.Requires(test != null);

            return TestRunner.RunItems<Tuple<TArg1>>(
                testItems,
                testItem => test(testItem.Item1));
        }

        /// <summary>
        /// Runs the test against the test items that have already been added.
        /// </summary>
        /// <typeparam name="TArg1">The type of the first argument.</typeparam>
        /// <typeparam name="TArg2">The type of the second argument.</typeparam>
        /// <param name="testItems">The test items.</param>
        /// <param name="test">The test to run.</param>
        /// <returns>The collection of items.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design.")]
        public static ListItemCollection<Tuple<TArg1, TArg2>> Run<TArg1, TArg2>(
            this ListItemCollection<Tuple<TArg1, TArg2>> testItems, Action<TArg1, TArg2> test)
        {
            Contract.Requires(testItems != null);
            Contract.Requires(test != null);

            return TestRunner.RunItems<Tuple<TArg1, TArg2>>(
                testItems,
                testItem => test(testItem.Item1, testItem.Item2));
        }

        /// <summary>
        /// Runs the test against the test items that have already been added.
        /// </summary>
        /// <typeparam name="TArg1">The type of the first argument.</typeparam>
        /// <typeparam name="TArg2">The type of the second argument.</typeparam>
        /// <typeparam name="TArg3">The type of the third argument.</typeparam>
        /// <param name="testItems">The test items.</param>
        /// <param name="test">The test to run.</param>
        /// <returns>The collection of items.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design.")]
        public static ListItemCollection<Tuple<TArg1, TArg2, TArg3>> Run<TArg1, TArg2, TArg3>(
            this ListItemCollection<Tuple<TArg1, TArg2, TArg3>> testItems,
            Action<TArg1, TArg2, TArg3> test)
        {
            Contract.Requires(testItems != null);
            Contract.Requires(test != null);

            return TestRunner.RunItems<Tuple<TArg1, TArg2, TArg3>>(
                testItems,
                testItem => test(testItem.Item1, testItem.Item2, testItem.Item3));
        }

        /// <summary>
        /// Runs the test against the test items that have already been added.
        /// </summary>
        /// <typeparam name="TArg1">The type of the first argument.</typeparam>
        /// <typeparam name="TArg2">The type of the second argument.</typeparam>
        /// <typeparam name="TArg3">The type of the third argument.</typeparam>
        /// <typeparam name="TArg4">The type of the fourth argument.</typeparam>
        /// <param name="testItems">The test items.</param>
        /// <param name="test">The test to run.</param>
        /// <returns>The collection of items.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design.")]
        public static ListItemCollection<Tuple<TArg1, TArg2, TArg3, TArg4>> Run<TArg1, TArg2, TArg3, TArg4>(
            this ListItemCollection<Tuple<TArg1, TArg2, TArg3, TArg4>> testItems,
            Action<TArg1, TArg2, TArg3, TArg4> test)
        {
            Contract.Requires(testItems != null);
            Contract.Requires(test != null);

            return TestRunner.RunItems<Tuple<TArg1, TArg2, TArg3, TArg4>>(
                testItems,
                testItem => test(testItem.Item1, testItem.Item2, testItem.Item3, testItem.Item4));
        }

        /// <summary>
        /// Runs the test against the test items that have already been added.
        /// </summary>
        /// <param name="test">The test to run.</param>
        public static void RunOnUIThread(Action test)
        {
            Contract.Requires(test != null);

            UIHelper.RunOnUIThread(test);
        }

        /// <summary>
        /// Runs the test against the test items that have already been added.
        /// </summary>
        /// <typeparam name="TArg1">The type of the first argument.</typeparam>
        /// <param name="testItems">The test items.</param>
        /// <param name="test">The test to run.</param>
        /// <returns>The collection of items.</returns>
        public static IEnumerable<TArg1> RunOnUIThread<TArg1>(this IEnumerable<TArg1> testItems, Action<TArg1> test)
        {
            Contract.Requires(testItems != null);
            Contract.Requires(test != null);

            testItems.Run(arg1 => UIHelper.RunOnUIThread(() => test(arg1)));
            return testItems;
        }

        /// <summary>
        /// Runs the test against the test items that have already been added.
        /// </summary>
        /// <typeparam name="TArg1">The type of the first argument.</typeparam>
        /// <param name="testItems">The test items.</param>
        /// <param name="test">The test to run.</param>
        /// <returns>The collection of items.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design.")]
        public static ListItemCollection<Tuple<TArg1>> RunOnUIThread<TArg1>(this ListItemCollection<Tuple<TArg1>> testItems, Action<TArg1> test)
        {
            Contract.Requires(testItems != null);
            Contract.Requires(test != null);

            return testItems.Run(arg1 => UIHelper.RunOnUIThread(() => test(arg1)));
        }

        /// <summary>
        /// Runs the test against the test items that have already been added.
        /// </summary>
        /// <typeparam name="TArg1">The type of the first argument.</typeparam>
        /// <typeparam name="TArg2">The type of the second argument.</typeparam>
        /// <param name="testItems">The test items.</param>
        /// <param name="test">The test to run.</param>
        /// <returns>The collection of items.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design.")]
        public static ListItemCollection<Tuple<TArg1, TArg2>> RunOnUIThread<TArg1, TArg2>(
            this ListItemCollection<Tuple<TArg1, TArg2>> testItems,
            Action<TArg1, TArg2> test)
        {
            Contract.Requires(testItems != null);
            Contract.Requires(test != null);

            return testItems.Run((arg1, arg2) => UIHelper.RunOnUIThread(() => test(arg1, arg2)));
        }

        /// <summary>
        /// Runs the test against the test items that have already been added.
        /// </summary>
        /// <typeparam name="TArg1">The type of the first argument.</typeparam>
        /// <typeparam name="TArg2">The type of the second argument.</typeparam>
        /// <typeparam name="TArg3">The type of the third argument.</typeparam>
        /// <param name="testItems">The test items.</param>
        /// <param name="test">The test to run.</param>
        /// <returns>The collection of items.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design.")]
        public static ListItemCollection<Tuple<TArg1, TArg2, TArg3>> RunOnUIThread<TArg1, TArg2, TArg3>(
            this ListItemCollection<Tuple<TArg1, TArg2, TArg3>> testItems,
            Action<TArg1, TArg2, TArg3> test)
        {
            Contract.Requires(testItems != null);
            Contract.Requires(test != null);

            return testItems.Run((arg1, arg2, arg3) => UIHelper.RunOnUIThread(() => test(arg1, arg2, arg3)));
        }

        /// <summary>
        /// Runs the test against the test items that have already been added.
        /// </summary>
        /// <typeparam name="TArg1">The type of the first argument.</typeparam>
        /// <typeparam name="TArg2">The type of the second argument.</typeparam>
        /// <typeparam name="TArg3">The type of the third argument.</typeparam>
        /// <typeparam name="TArg4">The type of the fourth argument.</typeparam>
        /// <param name="testItems">The test items.</param>
        /// <param name="test">The test to run.</param>
        /// <returns>The collection of items.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design.")]
        public static ListItemCollection<Tuple<TArg1, TArg2, TArg3, TArg4>> RunOnUIThread<TArg1, TArg2, TArg3, TArg4>(
            this ListItemCollection<Tuple<TArg1, TArg2, TArg3, TArg4>> testItems,
            Action<TArg1, TArg2, TArg3, TArg4> test)
        {
            Contract.Requires(testItems != null);
            Contract.Requires(test != null);

            return testItems.Run((arg1, arg2, arg3, arg4) => UIHelper.RunOnUIThread(() => test(arg1, arg2, arg3, arg4)));
        }
        #endregion Public Methods

        #region Private Methods
        private static void RunItems<TArg1>(IEnumerable<TArg1> items, Action<TArg1> test)
        {
            Contract.Requires(items != null);
            Contract.Requires(test != null);

            // Run each item.
            int itemNumber = 0;
            foreach (TArg1 item in items)
            {
                // Keep track of which item we are running.
                itemNumber++;

                // If an item fails then reraise the exception telling the user what items it failed with.
                try
                {
                    test(item);
                }
                catch (AssertInconclusiveException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    throw new TestRunnerException<Tuple<TArg1>>(new Tuple<TArg1>(item), itemNumber, ex);
                }
            }
        }

        private static ListItemCollection<TTuple> RunItems<TTuple>(ListItemCollection<TTuple> items, Action<TTuple> test)
        {
            Contract.Requires(items != null);
            Contract.Requires(test != null);

            // Run each item.
            int itemNumber = 0;
            foreach (TTuple item in items)
            {
                // Keep track of which item we are running.
                itemNumber++;

                // If an item fails then reraise the exception telling the user what items it failed with.
                try
                {
                    test(item);
                }
                catch (AssertInconclusiveException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    throw new TestRunnerException<TTuple>(item, itemNumber, ex);
                }
            }

            return items;
        }
        #endregion Private Methods
    }
}